﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Wizard.UI;
using Wizard.Controls;
using System.Threading;
using RoketPack.Management;
using System.Xml;
using System.Net;
using System.Security.Cryptography;
using RoketPack.Management.Objects;

namespace RoketPack.Recovery
{
    public partial class AutomaticRecoveryPage : InternalWizardPage
    {
        private List<string> m_Log = new List<string>();
        private RecoveryWizard m_Wizard = null;

        public AutomaticRecoveryPage(RecoveryWizard wizard)
        {
            InitializeComponent();

            this.m_Wizard = wizard;
        }

        private void ProtocolAssociationPage_SetActive(object sender, CancelEventArgs e)
        {
            this.SetWizardButtons(WizardButtons.None);

            // Start the recovery process.
            ThreadStart lambda = () =>
                {
                    try
                    {
                        List<Repository> repos = null;
                        this.m_Log.Add("Recovery started.");

                        // Recover the Repositories.xml document.
                        this.SetStatus("Checking repositories.xml document...");
                        if (!this.RecoverRepositoryXml(ref repos))
                            return;

                        this.SetStatus("Checking repository validity...");
                        // Recover the cached software lists.
                        if (!this.CheckRepositoriesXml(repos))
                            return;

                        this.SetStatus("Checking application validity...");
                        // Recover the applications.
                        if (!this.CheckApplicationsXml(repos))
                            return;

                        this.SetStatus("Disabling IM client...");
                        // Disable the IM client.
                        GlobalSettings s = GlobalSettings.Load();
                        s.IMPassword = "";
                        s.IMUsername = "";
                        s.Save();

                        // Now move to the next screen.
                        this.m_Log.Add("Recovery successful.");
                        RoketPack.Manager.VoidLambda switchlambda = () =>
                            {
                                this.GetWizard().Next();
                            };
                        if (this.InvokeRequired)
                            this.Invoke(switchlambda);
                        else
                            switchlambda();
                    }
                    catch (Exception ex)
                    {
                        Program.HandleUnhandledException(ex);
                        System.Windows.Forms.Application.Exit();
                    }
                };

            Thread thread = new Thread(lambda);
            thread.Name = "Recovery Process";
            thread.IsBackground = true;
            thread.Start();
        }

        private bool CheckApplicationsXml(List<Repository> repos)
        {
            try
            {
                foreach (Repository r in repos)
                {
                    // Ensure the repository is loaded correctly.
                    if (r.Applications == null)
                        continue;

                    foreach (RoketPack.Management.Application a in r.Applications.Values)
                    {
                        this.SetStatus("Checking application validity...", a.Name, true);

                        // Ensure there aren't any files which don't have instversion=""
                        // when this application is installed.
                        foreach (RoketPack.Management.Component c in a.Components.Values)
                        {
                            bool installed = c.IsInstalled();

                            // Check files
                            foreach (File f in c.Files)
                            {
                                if (f.InstalledVersion == -1 && !f.DeleteOnNextUpdate && installed)
                                {
                                    if (RecoveryUtil.RecoverVersionInformation(r, a, c, f))
                                        this.Log("[recovered] " + f.Local);
                                    else
                                    {
                                        f.InstalledVersion = this.GetManualVersionSelection(a, c, f.AvailableVersion);
                                        this.Log("[manual] " + f.Local);
                                    }
                                }
                            }

                            // Check folders
                            foreach (Folder f in c.Folders)
                            {
                                if (f.InstalledVersion == -1 && !f.DeleteOnNextUpdate && installed)
                                {
                                    if (RecoveryUtil.RecoverVersionInformation(r, a, c, f))
                                        this.Log("[recovered] " + f.Local);
                                    else
                                    {
                                        f.InstalledVersion = this.GetManualVersionSelection(a, c, f.AvailableVersion);
                                        this.Log("[manual] " + f.Local);
                                    }
                                }
                            }

                            // Check links
                            foreach (Link f in c.Links)
                            {
                                if (f.InstalledVersion == -1 && !f.DeleteOnNextUpdate && installed)
                                {
                                    if (RecoveryUtil.RecoverVersionInformation(r, a, c, f))
                                        this.Log("[recovered] " + f.Local);
                                    else
                                    {
                                        f.InstalledVersion = this.GetManualVersionSelection(a, c, f.AvailableVersion);
                                        this.Log("[manual] " + f.Local);
                                    }
                                }
                            }

                            // Check classic installer
                            if (c.ClassicInstaller != null)
                            {
                                if (c.ClassicInstaller.InstalledVersion == -1 && !c.ClassicInstaller.DeleteOnNextUpdate && installed)
                                {
                                    if (RecoveryUtil.RecoverVersionInformation(r, a, c, c.ClassicInstaller))
                                        this.Log("[recovered] <classic installer>");
                                    else
                                    {
                                        c.ClassicInstaller.InstalledVersion = this.GetManualVersionSelection(a, c, c.ClassicInstaller.AvailableVersion);
                                        this.Log("[manual] <classic installer>");
                                    }
                                }
                            }
                        }

                        // Now synchronize the new data back to the application cache.
                        a.SyncCache();
                    }
                    try
                    {
                        r.Initalize();
                    }
                    catch (WebException ex)
                    {
                        this.RecoveryFailure(
                            "The remote URL " + r.Url + " is missing the software.xml or access was denied to the resource.", ex);
                        return false;
                    }
                    catch (CryptographicException ex)
                    {
                        this.RecoveryFailure(
                            "The remote URL " + r.Url + " has an invalid SSL certificate.", ex);
                        return false;
                    }
                }
            }
            catch (Exception ex)
            {
                this.RecoveryFailure(
                    "A general exception was raised while verifing repositories.", ex);
                return false;
            }

            return true;
        }

        private int GetManualVersionSelection(RoketPack.Management.Application a, RoketPack.Management.Component c, int version)
        {
            int ret = -1;

            RoketPack.Manager.VoidLambda lambda = () =>
            {
                string old = this.c_RecoveryStatusLabel.Text;
                this.c_RecoveryStatusLabel.Text = "Waiting for user...";
                this.c_ManualVersionSelectionGroupBox.Visible = true;
                this.c_VersionSelectionReasonLabel.Text = this.GetVersionReasonLabel(a, c, version);
                EventHandler first = null;
                EventHandler latest = null;
                first = new EventHandler((sender, e) =>
                {
                    this.c_RecoveryStatusLabel.Text = old;
                    this.c_FirstVersionButton.Click -= first;
                    this.c_ManualVersionSelectionGroupBox.Visible = false;
                    ret = 0;
                });
                latest = new EventHandler((sender, e) =>
                {
                    this.c_RecoveryStatusLabel.Text = old;
                    this.c_LatestVersionButton.Click -= latest;
                    this.c_ManualVersionSelectionGroupBox.Visible = false;
                    ret = version;
                });
                this.c_FirstVersionButton.Click += first;
                this.c_LatestVersionButton.Click += latest;

            };

            this.Invoke(lambda);

            while (ret == -1)
                Thread.Sleep(1);

            return ret;
        }

        private string GetVersionReasonLabel(RoketPack.Management.Application a, RoketPack.Management.Component c, int available)
        {
            return @"There was no version information available for a file or folder in:
  * Application: " + a.Name + ", Component: " + c.Name + @"
In order to correct this situation, you need to select whether the version should be set
to the First version that was available (version 0), or the Latest version (version " + available + @").
The former will cause the component to be updated, the latter not (as it will be up-to-date).";
        }

        private bool CheckRepositoriesXml(List<Repository> repos)
        {
            try
            {
                foreach (Repository r in repos)
                {
                    this.SetStatus("Checking repository validity...", r.Name, true);
                    try
                    {
                        r.Initalize();
                    }
                    catch (WebException ex)
                    {
                        this.RecoveryFailure(
                            "The remote URL " + r.Url + " is missing the software.xml or access was denied to the resource.", ex);
                        return false;
                    }
                    catch (CryptographicException ex)
                    {
                        this.RecoveryFailure(
                            "The remote URL " + r.Url + " has an invalid SSL certificate.", ex);
                        return false;
                    }
                }
            }
            catch (Exception ex)
            {
                this.RecoveryFailure(
                    "A general exception was raised while verifing repositories.", ex);
                return false;
            }

            return true;
        }

        private bool RecoverRepositoryXml(ref List<Repository> repos)
        {
            try
            {
                try
                {
                    ConfigurationReader config = new ConfigurationReader();
                    repos = config.GetRepositoryList();
                }
                catch (XmlException)
                {
                    this.Log("Repositories.xml is corrupt, regenerating...");
                    RecoveryUtil.RegenerateRepositoryXml();
                    try
                    {
                        ConfigurationReader config = new ConfigurationReader();
                        repos = config.GetRepositoryList();
                        this.Log("Regenerated repositories.xml document.");
                    }
                    catch (XmlException ex)
                    {
                        this.RecoveryFailure(
                            "The repositories.xml document is still invalid, even after regenerating it.", ex);
                        return false;
                    }
                }
            }
            catch (Exception ex)
            {
                this.RecoveryFailure(
                    "A general exception was raised while attempting to recover the repositories.xml document.", ex);
                return false;
            }

            return true;
        }

        private void SetStatus(string s)
        {
            this.SetStatus(s, null, false);
        }

        private void SetStatus(string s, string p, bool sub)
        {
            if (s == null)
                throw new ArgumentException("s must not be null.", "s");

            RoketPack.Manager.VoidLambda lambda = () =>
            {
                if (p != null)
                    this.c_RecoveryStatusLabel.Text = s + " (" + p + ")";
                else
                    this.c_RecoveryStatusLabel.Text = s;
                this.c_RecoveryStatusLabel.Refresh();
            };
            if (this.c_RecoveryStatusLabel.InvokeRequired)
                this.c_RecoveryStatusLabel.Invoke(lambda);
            else
                lambda();

            if (p != null)
            {
                if (sub)
                    this.m_Log.Add("==> " + p);
                else
                    this.m_Log.Add("=> " + p);
            }
            else
            {
                if (sub)
                    this.m_Log.Add("==> " + s);
                else
                    this.m_Log.Add("=> " + s);
            }
        }

        private void Log(string p)
        {
            this.m_Log.Add("==> " + p);
        }

        private void RecoveryFailure(string p, Exception ex)
        {
            this.m_Log.Add("*** " + p);
            this.m_Log.Add("Recovery failed.");
            if (ex != null)
            {
                this.m_Log.Add("=============================");
                this.m_Log.Add(ex.Message);
                foreach (string s in ex.StackTrace.Split(new char[] { '\n' }))
                    this.m_Log.Add(s);
                this.m_Log.Add("=============================");
            }

            // Now move to the next screen.
            RoketPack.Manager.VoidLambda switchlambda = () =>
            {
                this.GetWizard().Next();
            };
            if (this.InvokeRequired)
                this.Invoke(switchlambda);
            else
                switchlambda();
        }

        private void AutomaticRecoveryPage_WizardNext(object sender, WizardPageEventArgs e)
        {
            this.m_Wizard.RecoveryData.RecoveryLog = "";
            foreach (string m in this.m_Log)
                this.m_Wizard.RecoveryData.RecoveryLog += m + "\r\n";
        }
    }
}
